package org.lvxiao.example.redis.lock;

import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.script.DigestUtils;
import org.springframework.util.Assert;

public class RedisSimpleDistributionLock implements DistributionLock {

	private String comment = "if redis.call('SETNX', KEYS[1], 1) == 1 then redis.call('EXPIRE', KEYS[1], tonumber(KEYS[2])); return 1; end return 0;";
	private String sha1 = DigestUtils.sha1DigestAsHex(comment);
	private RedisConnectionFactory factory;
	private boolean isLoadScript = false;
	private int maxTryCount = 10;

	public RedisSimpleDistributionLock(RedisConnectionFactory factory) {
		Assert.notNull(factory, "factory is must");
		this.factory = factory;
	}

	@Override
	public String tryLock(String key, long msExpireTime) {
		RedisConnection connection = getConnection();
		try {
			//TODO : 优化成 set xx ex | nx 
			long result = connection.evalSha(getScriptSha(), ReturnType.INTEGER, 2, key.getBytes(),
					String.valueOf(msExpireTime / 1000).getBytes());
			return result == 0 ? null : key;
		} finally {
			connection.close();
		}

	}

	private void loadScript() {
		if (!isLoadScript) {
			synchronized (sha1) {
				if (!isLoadScript) {
					RedisConnection connection = getConnection();
					try {
						connection.scriptLoad(comment.getBytes());
					} finally {
						connection.close();
					}

				}
			}
		}
	}

	private String getScriptSha() {
		loadScript();
		return sha1;
	}

	public byte[] longIntoBytes(long l) {
		byte[] result = new byte[8];

		for (int i = 7; i >= 0; i--) {
			result[i] = (byte) (l & 0xffL);
			l >>= 8;
		}
		return result;

	}

	@Override
	public String lock(String key, long msExpireTime) {
		String result = null;
		for (int i = 0; i < maxTryCount && result == null;) {
			result = tryLock(key, msExpireTime);
		}

		if (result == null) {
			throw new RuntimeException("获取锁失败， 重试次数过多");
		}
		return result;
	}

	@Override
	public String lock(String key, long msExpireTime, long blockTime) {

		long beginTime = System.currentTimeMillis();
		String result = null;

		while ((result = tryLock(key, msExpireTime)) == null) {
			long diffTime = System.currentTimeMillis() - beginTime;

			if (diffTime >= blockTime) {
				throw new RuntimeException("获取锁失败， 已超时");
			}
		}

		return result;
	}

	@Override
	public boolean unlock(String lockVersionKey) {
		Long delete = getConnection().del(lockVersionKey.getBytes());
		return delete > 0 ;
	}

	private RedisConnection getConnection() {
		return factory.getConnection();
	}

}
